/*
 * Copyright 2024 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.pajamas.starter.integration.module.jpa;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.pajamas.starter.core.entity.PajamasEntity;
import org.pajamas.starter.core.repo.PajamasRepo;
import org.pajamas.starter.core.repo.PajamasRepoConcurrentModifyException;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import javax.annotation.Nullable;
import javax.persistence.EntityManager;

/**
 * @author william
 * @since 2023/12/14
 * <p>
 *        TODO optimize batch operation
 */
@Repository
@Transactional(readOnly = true)
public class PajamasRepoImpl<T extends PajamasEntity> extends SimpleJpaRepository<T, Long> implements PajamasRepo<T> {
    public PajamasRepoImpl(JpaEntityInformation<T, Long> entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);
    }

    @Transactional
    @Override
    public int deleteByIdSoftly(@Nullable Long id) {
        if (id == null) {
            return 0;
        }

        Optional<T> optional = this.findById(id);
        if (!optional.isPresent()) {
            throw new PajamasJpaDataException("deleteByIdSoftly.optional is not present, id:" + id);
        }
        T entity = optional.get();

        Long del = entity.getDel();
        if (del != null) {
            if (id.equals(del)) {
                throw new PajamasRepoConcurrentModifyException("deleteByIdSoftly.entity was already deleted(del=id), " +
                        "id:" + id);
            }
            if (!del.equals(0L)) {
                throw new PajamasJpaDataException("entity data is not consistent(del!=id), id:" + id);
            }
        }

        entity.setDel(id);
        T res = this.save(entity);
        if (!id.equals(res.getDel())) {
            throw new PajamasRepoConcurrentModifyException("deleteByIdSoftly.entity deletes fail(id!=del), id:" + id);
        }
        return 1;
    }

    @Override
    public int deleteByIdSoftly(@Nullable Collection<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            return 0;
        }
        Set<Long> effectiveIds = ids
                .stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        if (CollectionUtils.isEmpty(ids)) {
            throw new PajamasJpaDataException("deleteByIdSoftly.effectiveIds is empty");
        }
        if (effectiveIds.size() != ids.size()) {
            throw new PajamasJpaDataException("deleteByIdSoftly.ids exist null id, effectiveIds.size != ids.size");
        }

        List<T> entities = this.findAllById(effectiveIds);
        if (CollectionUtils.isEmpty(entities)) {
            throw new PajamasJpaDataException("deleteByIdSoftly.effectiveIds is empty");
        }
        if (entities.size() != ids.size()) {
            throw new PajamasJpaDataException("deleteByIdSoftly.ids exist id not found, entities.size != ids.size");
        }

        boolean delAnyNeZero = entities
                .stream()
                .filter(Objects::nonNull)
                .map(T::getDel)
                .filter(Objects::nonNull)
                .anyMatch(del -> !del.equals(PajamasEntityListener.DEL_DEFAULT));
        if (delAnyNeZero) {
            throw new PajamasRepoConcurrentModifyException("deleteByIdSoftly.entities exist any del != 0");
        }

        entities = entities
                .stream()
                .filter(Objects::nonNull)
                .peek(entity -> entity.setDel(entity.getId()))
                .collect(Collectors.toList());
        entities = this.saveAll(entities);
        boolean delAnyNeId = entities
                .stream()
                .anyMatch(entity -> ObjectUtils.notEqual(entity.getId(), entity.getDel()));
        if (delAnyNeId) {
            throw new PajamasRepoConcurrentModifyException("deleteByIdSoftly.entities exist any del save fail");
        }
        if (entities.size() != ids.size()) {
            throw new PajamasRepoConcurrentModifyException("deleteByIdSoftly.entities.size != ids.size");
        }
        return ids.size();
    }
}
